Skip to content

04 文本分析器 - awk

  • AWK 由两位贝尔实验室的计算机科学家 Alfred AhoPeter Weinberger 以及著名的计算机科学家 Brian Kernighan 共同开发。
  • 其命名取自三位发明人姓的首字母 Aho、Weinberger 和 Kernighan。
  • awk 是一种命令行工具,用于处理和操作文本文件。
  • awk 用于模式扫描和处理,它能够对文本文件进行复杂的文本分析和处理。
  • awk 通过提供编程语言的功能,如变量、数学运算、字符串处理等,使得对文本文件的分析和操作变得非常灵活和高效。

详细教程:Linux awk 命令 | 菜鸟教程

基本语法

bash
awk 'pattern {action}' file
  • options:选项,用于控制 awk 的行为。
  • pattern:一个定义了哪些行会被处理的正则表达式或条件表达式。
  • action:一组在匹配到 pattern 的行上执行的命令,封闭在大括号 {} 中。
  • file:待处理的文件名。

TIP

如果没有指定 pattern,则 action 会应用于所有行。如果没有指定 action,则默认的行为是打印每个匹配的行。

Options 参数

选项描述
-F <分隔符>--field-separator=<分隔符>指定输入字段的分隔符,默认是空格。使用这个选项可以指定不同于默认分隔符的字段分隔符。
-v <变量名>=<值>设置 awk 内部的变量值。可以使用该选项将外部值传递给 awk 脚本中的变量。
-f <脚本文件>指定一个包含 awk 脚本的文件。这样可以在文件中编写较大的 awk 脚本,然后通过 -f 选项将其加载。
-V--version显示 awk 的版本信息。
-h--help显示 awk 的帮助信息,包括选项和用法示例。

自定义变量

  • -v varname=value ,变量名区分字符大小写。
  • action 中直接定义。

基本概念

模式 - 动作语法

AWK 的基本结构是 pattern { action },即对于每一个符合模式的输入行,执行相应的动作。模式和动作部分都是可选的。

  • 模式:可以是正则表达式、关系表达式、组合表达式等。
  • 动作:由一系列的 AWK 语句组成,用于处理匹配的行。

字段和记录

  • 记录:默认情况下,AWK 将每一行视为一个记录。
  • 字段:每条记录可以分为多个字段,默认以空白(空格或制表符)分隔字段。字段使用 $1, $2, ... $n 表示,$0 表示整个记录。

主要特性

  1. 模式匹配awk 使用扩展的正则表达式来匹配文本行,从而进行特定的处理。
  2. 内置变量和操作符:提供了丰富的内置变量(如NR表示当前行号,NF表示当前行的字段数)和操作符,便于处理文本和数值数据。
  3. 字段处理能力awk 默认将每行文本分割成多个字段,字段之间默认由空白字符分隔。用户可以轻松访问和操作这些字段。
  4. 编程元素:提供了完整的编程语言功能,包括变量、条件语句、循环控制、数组、函数等。
  5. 文本和数值函数:内置许多用于操作文本字符串和数值的函数,如 substr(), length(), int(), sin(), 等等。

关键特性

  1. 字段和记录分隔符:

    • 默认情况下,awk 将字段(变量$1,$2, …)视为由空格或制表符分隔的单词。
    • 可以使用 -F 选项或 FS 内置变量来指定字段分隔符。
    • 记录分隔符默认是换行符,可以使用 RS 内置变量来更改。
  2. 内置变量:

    变量描述
    FS (Field Separator)输入字段分隔符,默认为空白字符(空格或制表符)。
    OFS (Output Field Separator)输出字段分隔符,默认为空白字符(空格或制表符)。
    RS (Record Separator)输入记录分隔符,默认为换行符(\n),指定输入时的换行符。
    ORS (Output Record Separator)输出记录分隔符,默认为换行符(\n),输出时用指定符号代替换行符。
    NF (Number of Fields)当前行的字段个数,即当前行被分割成了几列。
    NR (Number of Records)行号,表示当前处理的文本行的总行号。
    FNR (File Number of Records)表示各文件分别计数的行号。
    ARGC (Argument Count)命令行参数的个数。
    ARGV (Argument Vector)数组,保存的是命令行所给定的各参数。
    $n当前记录的第 n 个字段,字段间由 FS 分隔。
    $0完整的输入记录。
    ARGIND (Argument Index)命令行中当前文件的位置 (从 0 开始算)。
    CONVFMT (Conversion Format)数字转换格式 (默认值为 %.6g)。
    ENVIRON (Environment)环境变量关联数组。
    ERRNO (Error Number)最后一个系统错误的描述。
    FIELDWIDTHS (Field Widths)字段宽度列表 (用空格键分隔)。
    FILENAME (Filename)当前文件名。
    IGNORECASE (Ignore Case)如果为真,则进行忽略大小写的匹配。
    OFMT (Output Format)数字的输出格式 (默认值是 %.6g)。
    RLENGTH (Match Length)match 函数所匹配的字符串的长度。
    RSTART (Match Start)match 函数所匹配的字符串的第一个位置。
    SUBSEP (Subscript Separator)数组下标分隔符 (默认值是 \034)。
  3. 模式匹配:

    • 可以使用正则表达式作为模式。
    • /pattern/:模式匹配。
    • ! /pattern/:模式不匹配。
  4. 条件语句:

    • ifelse ifelse
  5. 循环:

    • forwhiledo-while

示例

简单示例

bash
Name    Age   City
Alice   25    New York
Bob     30    Los Angeles
Charlie 35    Chicago
bash
name,age,city
Alice,25,New York
Bob,30,Los Angeles
Charlie,35,Chicago
David,28,San Francisco
  1. 打印所有行

    bash
      ~ awk '{print}' data.txt
    Name    Age   City
    Alice   25    New York
    Bob     30    Los Angeles
    Charlie 35    Chicago
  2. 打印特定字段

    bash
      ~ awk '{print $1, $3}' data.txt # 只打印每行的姓名和城市
    Name City
    Alice New
    Bob Los
    Charlie Chicago
  3. 条件打印

    bash
      ~ awk '$2 > 30 {print $1, $2}' data.txt # 只打印年龄大于 30 的记录
    Name Age
    Charlie 35
      ~ awk '/Los|New/ {print}' data.txt # 打印包含 Los 或者 New 的行
    Alice   25    New York
    Bob     30    Los Angeles
      ~ awk '{
        if ($2 > 30)
            print $1 " ("$2 ") is older than 30";
        else
            print $1 " ("$2 ") is not older than 30";
    }' data.txt # 根据年龄是否大于 30 来打印不同的消息
    Name (Age) is older than 30
    Alice (25) is not older than 30
    Bob (30) is not older than 30
    Charlie (35) is older than 30
      ~ cat data.csv | awk -F, '$2 ~ /^[0-9]+$/{print $1,$2}' | awk  'max < $2 {max = $2} END {print max}' # 打印 data.csv 文件中 age 中的最大值
    35
  4. 使用内置变量

    bash
      ~ awk '{print NR, $0}' data.txt # 打印每行的行号和该行的内容
    1 Name    Age   City
    2 Alice   25    New York
    3 Bob     30    Los Angeles
    4 Charlie 35    Chicago
  5. 统计文件的行数

    bash
      ~ awk 'END {print NR}' data.txt
    4
  6. 文本加工

    bash
      ~ awk '{temp = $1; $1 = $2; $2 = temp; print}' data.txt # 将每行的第一个字段和第二个字段的值交换
    Age Name City
    25 Alice New York
    30 Bob Los Angeles
    35 Charlie Chicago
      ~ awk '{sum += $2} END {print sum}' data.txt # 对第二字段进行求和
    90
      ~ awk -F, 'BEGIN {OFS="\t"} NR==1 {print $0} NR>1 {$2=$2+5; print $0}' data.csv # 对 data.csv 中的 age 字段 +5
    name,age,city
    Alice   35      New York
    Bob     30      Los Angeles
    Charlie 40      Chicago
    David   33      San Francisco
      ~ awk -F, -va=5 'BEGIN {OFS="\t"} NR==1 {print $0} NR>1 {$2=$2+a; print $0}' data.csv
    name,age,city
    Alice   35      New York
    Bob     30      Los Angeles
    Charlie 40      Chicago
    David   33      San Francisco
  7. 修改字段分隔符(例如,如果输入文件用逗号分隔)

    bash
      ~ awk -F, '{print $1, $2}' data.csv
    name age
    Alice 25
    Bob 30
    Charlie 35
    David 28
      ~ awk -F, '$2 ~ /^[0-9]+$/{print $1,$2}' data.csv # 排除第一行
    Alice 25
    Bob 30
    Charlie 35
    David 28

常用示例

bash
  ~ top -n 2| grep %Cpu | tail -n 1 | awk -F "," '{print $4}'
 99.4 id
  1. top -n 2: 运行 top 命令,-n 2 参数表示只显示两次 top 的输出结果,然后退出。这个命令通常用于查看系统资源的使用情况,如 CPU、内存等。
  2. | grep %Cpu: 使用管道符 |top 的输出传递给 grep 命令,用于过滤包含 %Cpu 的行。%Cputop 输出中用于显示 CPU 使用率的部分。
  3. | tail -n 1: 使用管道将 grep 的输出传递给 tail 命令,-n 1 参数表示只取最后一行。这是因为 top 命令的输出中可能会包含多行 %Cpu 的信息,我们只需要最后一行。
  4. | awk -F "," '{print $4}': 最后,使用管道将 tail 的输出传递给 awk 命令。-F "," 参数指定 awk 使用逗号作为字段分隔符,'{print $4}' 则表示打印第四个字段。

综合起来,这条命令的作用是从 top 命令的输出中获取当前 CPU 使用率的信息。具体来说,它提取了 %Cpu 行中第四个字段的值,通常这个字段包含的是 CPU 的使用率百分比。

bash
  ~ vim lol.info
  ~ cat lol.info
name: 亚索
city: 诺克萨斯
name: 盖伦
city: 德玛西亚
name:
city: 皮尔特沃夫
name: 提莫
city: 艾欧尼亚
name: 凯南
city: 班德尔城
name: 琴女
city: 诺克萨斯
  ~ cat lol.info | awk '/^name/{name=$0;next;} {print name " - " $0 }'
name: 亚索 - city: 诺克萨斯
name: 盖伦 - city: 德玛西亚
name: - city: 皮尔特沃夫
name: 提莫 - city: 艾欧尼亚
name: 凯南 - city: 班德尔城
name: 琴女 - city: 诺克萨斯
  ~ awk 'BEGIN {FS=": "; OFS=" - "} /^name/ {name = $2} /^city/ {print "name: " name OFS "city: " $2}' lol.info
name: 亚索 - city: 诺克萨斯
name: 盖伦 - city: 德玛西亚
name: - city: 皮尔特沃夫
name: 提莫 - city: 艾欧尼亚
name: 凯南 - city: 班德尔城
name: 琴女 - city: 诺克萨斯
  • /^name/ {name = $0; next}: 当遇到以 "name" 开头的行时,将整行内容赋给变量 name,然后跳过当前循环(next)。
  • {print name " - " $0}: 对于其他行(即不以 "name" 开头的行),打印之前保存的 name 变量和当前行内容的组合。

  • BEGIN {FS=": "; OFS=" - "}: 在处理开始前,设置字段分隔符 FS": ",设置输出字段分隔符 OFS" - "。这样设置之后,输入的每一行会按照 ": " 分割字段,输出时会用 " - " 连接字段。
  • /^name/ {name = $2}: 当遇到以 "name" 开头的行时,将第二个字段的值赋给变量 name
  • /^city/ {print "name: " name OFS "city: " $2}: 当遇到以 "city" 开头的行时,打印输出 "name: " name OFS "city: " $2。这里 name 是之前保存的英雄姓名,$2 是当前行的城市信息。

整体模式

bash
awk 'BEGIN{commands} pattern{commands} END{commands}'
  • BEGIN{commands}-只会执行一次,常用于变量初始化,打印表头等信息
  • pattern{commands}- 以行为单位处理的
  • END{commands}-只会执行一次,常用于统计结果打印
bash
  ~ cat data.csv
name,age,city
Alice,30,New York
Bob,25,Los Angeles
Charlie,35,Chicago
David,28,San Francisco
  ~ cat data.csv | head -1  | awk 'BEGIN{ print "BEGIN" } { print $0 } END{ print "END" }'
BEGIN
name,age,city
END
  ~ cat data.csv |tail -1  | awk 'BEGIN{ print "BEGIN" } { print $0 } END{ print "END" }'
BEGIN
David,28,San Francisco
END

示例:将 top 命令输出中前六个进程的 PID、%CPU、%MEM 和 COMMAND 信息输出

bash
  ~ top -n 1 -b | awk 'NR > 6 && NR <= 13 {print $1, $9, $10, $12}' OFS=" - " # 以 " - " 分隔

PID - %CPU - %MEM - COMMAND
1584097 - 6.7 - 0.1 - top
1 - 0.0 - 0.3 - systemd
2 - 0.0 - 0.0 - kthreadd
3 - 0.0 - 0.0 - pool_workqueu+
4 - 0.0 - 0.0 - kworker/R-rcu+
5 - 0.0 - 0.0 - kworker/R-rcu+
  ~ top -n 1 -b | awk 'NR > 6 && NR <= 13 {printf "%-8s - %-5s - %-5s - %s\n", $1, $9, $10, $12}'

PID      - %CPU  - %MEM  - COMMAND
1        - 0.0   - 0.3   - systemd
2        - 0.0   - 0.0   - kthreadd
3        - 0.0   - 0.0   - pool_workqueu+
4        - 0.0   - 0.0   - kworker/R-rcu+
5        - 0.0   - 0.0   - kworker/R-rcu+
6        - 0.0   - 0.0   - kworker/R-slu+
  ~ top -n 1 -b | awk 'NR > 6 && NR <= 13 {printf "%8s - %5s - %5s - %s\n", $1, $9, $10, $12}'

     PID -  %CPU -  %MEM - COMMAND
       1 -   0.0 -   0.3 - systemd
       2 -   0.0 -   0.0 - kthreadd
       3 -   0.0 -   0.0 - pool_workqueu+
       4 -   0.0 -   0.0 - kworker/R-rcu+
       5 -   0.0 -   0.0 - kworker/R-rcu+
       6 -   0.0 -   0.0 - kworker/R-slu+

  ~ top -n 1 -b | awk 'NR > 6 && NR <= 13 {print $1 "\t" $9 "\t" $10 "\t" $12}'
PID     %CPU    %MEM    COMMAND
1       0.0     0.3     systemd
2       0.0     0.0     kthreadd
3       0.0     0.0     pool_workqueu+
4       0.0     0.0     kworker/R-rcu+
5       0.0     0.0     kworker/R-rcu+
6       0.0     0.0     kworker/R-slu+
  ~ top -n 1 -b | awk 'NR > 6 && NR <= 13 {print $1, $9, $10, $12}' OFS="\t"
PID     %CPU    %MEM    COMMAND
1       0.0     0.3     systemd
2       0.0     0.0     kthreadd
3       0.0     0.0     pool_workqueu+
4       0.0     0.0     kworker/R-rcu+
5       0.0     0.0     kworker/R-rcu+
6       0.0     0.0     kworker/R-slu+
  • top -n 1 -b: 运行 top 命令,-n 1 表示只运行一次,-b 表示以批处理模式运行,输出结果不会被交互式控制台影响。
  • awk 'NR > 6 && NR <= 13': 使用 awk 处理 top 命令的输出,NR > 6 && NR <= 13 表示处理行号大于 6 且小于等于 13 的行(即舍弃前 6 行,然后处理接下来的七行)。
  • printf "%-8s - %-5s - %-5s - %s\n":使用 printf 函数来格式化输出。%-8s 表示左对齐且占用 8 个字符的字符串格式。这样,第一列(PID)、第二列(%CPU)、第三列(%MEM)和第四列(COMMAND)的宽度都会是 8 个字符,保证了对齐性。
  • $1, $9, $10, $12:这些是 awk 命令中指定的列,分别对应 PID、%CPU、%MEM 和 COMMAND。

示例:监控并计算在 6 秒内系统 CPU 时间 (sys 时间) 的平均值。

bash
  ~ top -n 7 -d 1 -b | grep '%Cpu' | tail -6 | awk '{print; sum += $4; printf "第 %d 秒的 sys 时间:%.2f\n", NR, $4} END {printf "\n6 秒钟 top 信息 sys 时间的平均值:%.2f\n", sum / NR}'
%Cpu(s):  0.2 us,  0.7 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
 1 秒的 sys 时间:0.70
%Cpu(s):  0.2 us,  0.7 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
 2 秒的 sys 时间:0.70
%Cpu(s):  0.5 us,  1.5 sy,  0.0 ni, 97.0 id,  0.7 wa,  0.0 hi,  0.2 si,  0.0 st
 3 秒的 sys 时间:1.50
%Cpu(s):  0.2 us,  0.5 sy,  0.0 ni, 97.2 id,  2.0 wa,  0.0 hi,  0.0 si,  0.0 st
 4 秒的 sys 时间:0.50
%Cpu(s):  0.2 us,  0.5 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
 5 秒的 sys 时间:0.50
%Cpu(s):  0.2 us,  0.5 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
 6 秒的 sys 时间:0.50

6 秒钟 top 信息 sys 时间的平均值:0.73
  ~ top -n 7 -d 1 -b | grep '%Cpu' | tail -6 | awk '
    {
        print
        sum += $4
        printf "第 %d 秒的 sys 时间:%.2f\n", NR, $4
    }
    END {
        printf "\n6 秒钟 top 信息 sys 时间的平均值:%.2f\n", sum / NR
    }
'
%Cpu(s):  0.0 us,  0.5 sy,  0.0 ni, 99.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
 1 秒的 sys 时间:0.50
%Cpu(s):  0.2 us,  0.2 sy,  0.0 ni, 99.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
 2 秒的 sys 时间:0.20
%Cpu(s):  0.2 us,  0.7 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
 3 秒的 sys 时间:0.70
%Cpu(s):  0.5 us,  0.7 sy,  0.0 ni, 97.5 id,  1.2 wa,  0.0 hi,  0.0 si,  0.0 st
 4 秒的 sys 时间:0.70
%Cpu(s):  0.0 us,  0.5 sy,  0.0 ni, 99.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
 5 秒的 sys 时间:0.50
%Cpu(s):  0.5 us,  0.5 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
 6 秒的 sys 时间:0.50

6 秒钟 top 信息 sys 时间的平均值:0.52
  • top -n 7 -d 1 -b

    • top 命令提供正在运行的进程的实时视图。
    • -n 7 表示 top 将更新 7 次。因为第一次更新通常是初始数据采集,通常不计入计算,所以剩下的 6 次更新被用于实际的 CPU 时间监控。
    • -d 1 设置每次更新之间的延迟为 1 秒。
    • -btop 以批处理模式运行,适合脚本解析输出。
  • grep '%Cpu':过滤 top 输出,只保留包含 CPU 使用信息的行。

  • tail -6:选择过滤后的输出的最后 6 行,这些行对应于 6 秒内的 CPU 使用情况。

  • awk '{print; sum += $4; printf "第 %d 秒的 sys 时间:%.2f\n", NR, $4} END {printf "\n6 秒钟 top 信息 sys 时间的平均值:%.2f\n", sum / NR}'

    • print;:打印原始行。
    • sum += $4;:将第 4 列(对应于 sys CPU 时间)累加到 sum 中。
    • printf "第 %d 秒的 sys 时间:%.2f\n", NR, $4;:用中文打印每秒的 sys 时间。
    • END 块中,处理完所有行后,打印 6 秒内 sys CPU 时间的平均值。

相关教程